Jump to content

Recommended Posts

Two classes, identical API: net.WebSocketClient (ws://) and net.WebSocketClientTls (wss://).

namespace net {

interface WebSocketClient {
    void connect(string url, [table headers]);
    void send(string data);
    void sendBinary(string data);
    bool isOpen();
    void close();

    void addEventListener(string eventName, function handler);  // alias of registerEvent()
    void registerEvent(string eventName, function handler);
    void unregisterEvent(string eventName);  // TODO: not callable from Lua yet

    event connected;
    event disconnected;
    event dataReceived;
    event error;
}

} // namespace net

Connect

void connect(string url, [table headers]);
  • url - ws://host[:port]/path for WebSocketClient, wss://... for WebSocketClientTls.
  • headers - a table of header name/value pairs added to the handshake request, e.g. Origin or
    Authorization.

Sec-WebSocket-Protocol is handled specially: its value is split on , and each entry is registered as a
requested subprotocol, so it takes part in the library’s subprotocol handling instead of being sent as an
opaque header. The request on the wire is unchanged. An entry that is not a valid HTTP token raises a Lua
error from connect().

self.sock:connect("wss://broker.example.com/mqtt", {
  ["Sec-WebSocket-Protocol"] = "mqtt",
  ["Authorization"] = "Bearer " .. token,
})

connected event - fired once the handshake completes. No arguments.

disconnected event - fired when the connection is closed by either side. No arguments.

Send

void send(string data);

Sends a text frame. RFC 6455 requires a text payload to be valid UTF-8; if data is not, nothing is
sent and the error event fires with a message pointing at sendBinary().

void sendBinary(string data);

Sends a binary frame, with no constraint on the bytes. Use this for any protocol that is not UTF-8
text - MQTT over WebSocket, for instance, mandates binary frames.

Lua strings are byte strings, so both methods take the same type; the method chosen decides the frame
opcode.

self.sock:send("hello")        -- text frame
self.sock:sendBinary(packet)   -- binary frame, e.g. an MQTT CONNECT

packet above is just a Lua string holding the raw bytes of the protocol frame, assembled with
string.char. An MQTT 3.1.1 CONNECT, for example - a 2 byte length prefix before every string field, and a
varint Remaining Length after the fixed header byte:

-- Remaining Length: 7 payload bits per byte, MSB set while more bytes follow.
local function varint(n)
  local out = {}
  repeat
    local byte = n % 128
    n = n // 128
    if n > 0 then byte = byte + 128 end
    out[#out + 1] = string.char(byte)
  until n == 0
  return table.concat(out)
end

-- MQTT string field: 2 byte big-endian length, then the raw bytes.
local function mqttString(text)
  return string.char(#text // 256, #text % 256) .. text
end

local function connectPacket(clientId, keepAlive)
  local variableHeader = mqttString("MQTT")  -- protocol name
      .. string.char(0x04)                   -- protocol level 4 = MQTT 3.1.1
      .. string.char(0x02)                   -- connect flags: clean session
      .. string.char(keepAlive // 256, keepAlive % 256)
  local payload = mqttString(clientId)
  local body = variableHeader .. payload

  return string.char(0x10) .. varint(#body) .. body  -- 0x10 = CONNECT
end

local packet = connectPacket("hc3-client", 60)
-- 10 16 00 04 4D 51 54 54 04 02 00 3C 00 0A 68 63 33 2D 63 6C 69 65 6E 74
self.sock:sendBinary(packet)

The same bytes would be rejected by send(): 0x10 and 0x04 are fine, but mqttString lengths and
binary flag bytes routinely fall outside UTF-8.

A received frame is decoded the same way, with one caveat: a WebSocket frame is not guaranteed to hold
exactly one protocol packet. It may hold several, or only part of one, so a dataReceived handler must
accumulate bytes and pull off whole packets rather than assume one frame == one packet.

self.sock:addEventListener("dataReceived", function(payload, isBinary)
  self.buffer = self.buffer .. payload
  -- ... peel complete MQTT packets off self.buffer, keep the remainder
end)

The same applies in the other direction: one sendBinary() call is one frame, but a broker is free to
split or merge packets across frames, so never assume a one-to-one mapping.

Receive

dataReceived event arguments:

  • payload:string - the frame payload, bytes preserved.
  • isBinary:bool - true for a binary frame, false for a text frame.
self.sock:addEventListener("dataReceived", function(payload, isBinary) ... end)

Error Event

error event is fired when the handshake fails or a send is rejected. Argument:

  • message:string - human readable description.

Backwards compatibility

sendBinary() is additive and send()'s signature is unchanged, so existing callers are unaffected.

dataReceived’s second argument is safe to add: a handler is always a Lua function and Lua discards
surplus arguments, so a one-parameter handler is untouched. Only a function(...) varargs handler, or a
library function used directly as the handler, would observe it.

The Sec-WebSocket-Protocol rerouting keeps the request bytes identical - the requested list is joined
with ", ", the same canonical form the raw header used - and the subprotocol echoed back in the server’s
response is not validated, so no previously-working handshake can start failing.

One edge case is not compatible: a subprotocol containing a character that is not allowed in an HTTP token
(a space, /, :) is now rejected and connect() raises a Lua error, where such a value was previously
sent verbatim and ignored by the server. A malformed value that used to connect now raises. An empty value
now omits the header rather than sending it empty.

Remarks

Ping/pong, fragmentation and continuation frames are managed internally and are not exposed.
Closing the socket happens automatically when the object is finalized by Lua.

close() may be called before connected has fired. The socket is then closed as soon as the handshake
finishes, and connected is not delivered - only disconnected, once the connection is down.

Link to comment
https://forum.fibaro.com/topic/80470-mqtt-over-secure-websockets-documentation/
Share on other sites

  • K.Drozynski changed the title to MQTT over secure WebSockets - documentation
This topic is now closed to further replies.
×
×